// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ValidationAspects.Core;
using ValidationAspects.Exceptions;
using ValidationAspects.Sdk;

namespace ValidationAspects.Factories
{
	public class ValidateObject : IValidatorFactory
	{
		private static IEnumerable<Type> EnumerateTypes(Type type)
		{
			yield return type;

			foreach (Type interfaceType in type.GetInterfaces())
			{
				yield return interfaceType;
			}

			Type baseType = type.BaseType;
			while (baseType != null)
			{
				yield return baseType;
				baseType = baseType.BaseType;
			}
		}

		#region Implementation of IValidatorFactory
		public IValidator<T> Create<T>()
		{
			return new Validator<T>((v, c) =>
			                        {
			                        	// ReSharper disable CompareNonConstrainedGenericWithNull
			                        	if (null == v)
			                        		return;
			                        	// ReSharper restore CompareNonConstrainedGenericWithNull

			                        	List<ValidationException> exceptions = new List<ValidationException>();

			                        	foreach (Type type in EnumerateTypes(v.GetType()))
			                        	{
			                        		IValidator typeValidator = type.ResolveValidator();
			                        		if (typeValidator != null)
			                        		{
			                        			IValidationContext typeContext = new ValidationContext(v, type, null, null, null, null);
			                        			try
			                        			{
			                        				typeValidator.Invoke(v, typeContext);
			                        			}
			                        			catch (ValidationException e)
			                        			{
			                        				e.SetContext(typeContext);
			                        				exceptions.Add(e);
			                        			}
			                        		}

			                        		if (!ValidationSettings.StateValidateProperties)
			                        			continue;

			                        		foreach (PropertyInfo propertyInfo in type.GetAllProperties().Where(pi => pi.CanValidate()))
			                        		{
			                        			IValidator propertyValidator = propertyInfo.ResolveValidator();
			                        			if (propertyValidator == null)
			                        				continue;

			                        			object value = propertyInfo.GetValue(v, null);
			                        			IValidationContext propertyContext = new ValidationContext(v, propertyInfo, value);

			                        			try
			                        			{
			                        				propertyValidator.Invoke(value, propertyContext);
			                        			}
			                        			catch (ValidationException e)
			                        			{
			                        				e.SetContext(propertyContext);
			                        				exceptions.Add(e);
			                        			}
			                        		}
			                        	}

			                        	if (exceptions.Count > 0)
			                        	{
			                        		ValidationException exception = new ValidationContextException(new ValidationException[] {new ValidateObjectException(v)}.Concat(exceptions));
			                        		exception.SetContext(new ValidationContext(v, typeof(T), null, null, null, null));
			                        		throw exception;
			                        	}
			                        });
		}
		#endregion
	}
}